﻿using System;
using System.Windows;
using System.Windows.Controls;

namespace Gamelight.Sound
{
	public class SoundManager
	{
		private SoundPlayer[] soundPlayers;
		private MusicPlayer[] musicPlayers;
		private int soundPlayerIndex = 0;
		private int numSoundPlayers = 16;
		private int numMusicPlayers = 2;

		private static SoundManager instance = null;
		internal static SoundManager Instance { get { return SoundManager.instance; } }

		public static void StopMusic()
		{
			StopMusic(0);
		}

		public static void StopMusic(double fadeLengthInSeconds)
		{
			Music silence = new Music(null);
			if (fadeLengthInSeconds <= 0)
			{
				silence.Transition = MusicTransitionStyle.Instant;
			}
			else
			{
				silence.TransitionSeconds = fadeLengthInSeconds;
				silence.Transition = MusicTransitionStyle.FadeOutCurrentAndStop;
			}

			SoundManager.EnsureMusicPlaying(silence);
		}

		internal SoundManager(Scene.GameControl gameControl)
		{
			if (SoundManager.instance == null)
			{
				SoundManager.instance = this;
			}

			Grid mixerPanel = gameControl.SoundMixer;

			soundPlayers = new SoundPlayer[numSoundPlayers];
			for (int i = 0; i < numSoundPlayers; ++i)
			{
				soundPlayers[i] = new SoundPlayer();
				soundPlayers[i].MediaElement.Volume = 1;
				mixerPanel.Children.Add(soundPlayers[i].MediaElement);
			}

			musicPlayers = new MusicPlayer[numMusicPlayers];
			for (int i = 0; i < numMusicPlayers; ++i)
			{
				musicPlayers[i] = new MusicPlayer(this);
				mixerPanel.Children.Add(musicPlayers[i].MediaElement);
			}
		}

		private SoundPlayer GetNextAvailableSoundPlayer()
		{
			for (int i = 0; i < numSoundPlayers; ++i)
			{
				int index = (soundPlayerIndex + i) % numSoundPlayers;
				if (!soundPlayers[index].IsPlaying)
				{
					soundPlayerIndex = index;
					return soundPlayers[index];
				}
			}
			soundPlayerIndex++;
			return soundPlayers[soundPlayerIndex % numSoundPlayers];
		}

		public static void PlaySound(Sound sound)
		{
			SoundPlayer player = instance.GetNextAvailableSoundPlayer();
			player.Play(sound);
		}

		private Music currentlyPlaying = null;
		private int activeMusicPlayer = 0;
		private int transitionOutMusicPlayer = 1;

		public static void PlayMusic(Music music)
		{
			instance.transitionOutMusicPlayer = instance.activeMusicPlayer;
			instance.activeMusicPlayer++;
			instance.activeMusicPlayer = instance.activeMusicPlayer % instance.numMusicPlayers;

			instance.musicPlayers[instance.activeMusicPlayer].PlayMusic(music);
			instance.musicPlayers[instance.transitionOutMusicPlayer].EndMusic(music);

			instance.currentlyPlaying = music;
		}

		internal void Update()
		{
			foreach (MusicPlayer musicPlayer in instance.musicPlayers)
			{
				musicPlayer.Update();
			}
		}

		public static void EnsureMusicPlaying(Music music)
		{
			if (!music.Equals(instance.currentlyPlaying))
			{
				SoundManager.PlayMusic(music);
			}
		}

		private class SoundPlayer
		{
			public SoundPlayer()
			{
				this.MediaElement = new MediaElement()
				{
					Width = 1,
					Height = 1,
					Visibility = Visibility.Collapsed,
					AutoPlay = true
				};
			}

			public MediaElement MediaElement { get; private set; }
			public bool IsPlaying
			{
				get
				{
					return this.MediaElement.CurrentState == System.Windows.Media.MediaElementState.Playing;
				}
			}

			public void Play(Sound sound)
			{
				this.MediaElement.Stop();
				System.IO.Stream stream = sound.MediaStream;
				if (stream == null)
				{
					this.MediaElement.Source = null;
				}
				else
				{
					this.MediaElement.SetSource(stream);
				}
				this.MediaElement.Position = new TimeSpan(0);
				this.MediaElement.Play();
			}

			public int Volume
			{
				get
				{
					// do not want to return 0 if it's actually not 0. 
					if (this.MediaElement.Volume > 0 && this.MediaElement.Volume < 0.01)
					{
						return 1;
					}
					return (int)(this.MediaElement.Volume * 100);
				}
				set
				{
					int volume = System.Math.Max(0, System.Math.Min(100, value));
					this.MediaElement.Volume = volume / 100.0;
				}
			}
		}

		private class MusicPlayer : SoundPlayer
		{
			private enum PlayStatus
			{
				Looping,
				PlayingOnce,
				Stopped
			}

			private bool fadeIn = false;
			private bool fadeOut = false;
			private int transitionInLength = 1;
			private int transitionOutLength = 1;
			private int transitionInProgress = 0;
			private int transitionOutProgress = 0;

			private int previousPosition = 0;
			private int GetElapsedMilliseconds()
			{
				int currentPosition = (int)this.MediaElement.Position.TotalMilliseconds;
				int virtualCurrentPosition = currentPosition;
				if (previousPosition > currentPosition)
				{
					virtualCurrentPosition += (int)this.MediaElement.NaturalDuration.TimeSpan.TotalMilliseconds;
				}
				int ellapsed = virtualCurrentPosition - this.previousPosition;
				this.previousPosition = currentPosition;
				return ellapsed;
			}

			private SoundManager soundManager;

			public MusicPlayer(SoundManager soundManager) : base()
			{
				this.soundManager = soundManager;
				this.MediaElement.MediaEnded += new RoutedEventHandler(MediaElement_MediaEnded);
			}

			private PlayStatus status = PlayStatus.Stopped;

			private long  GetCurrentMilliseconds()
			{

				double vale = DateTime.Now.Subtract(new DateTime(2010, 4, 1)).TotalMilliseconds;

				return (long)vale;
			}

			public void Update()
			{
				int ellapsedTime = this.GetElapsedMilliseconds();

				if (this.EnqueuedMusic != null && this.GetCurrentMilliseconds() >= this.EnqueuedMusicStart)
				{
					this.KillThis(); // to reset to 0
					this.SetVolume(100);
					this.MediaElement.Play();
					this.EnqueuedMusic = null;
					this.EnqueuedMusicStart = 0;
				}

				if (fadeIn)
				{
					this.transitionInProgress = (int)this.MediaElement.Position.TotalMilliseconds;

					this.SetVolume(100 * this.transitionInProgress / this.transitionInLength);

					if (this.transitionInProgress >= this.transitionInLength)
					{
						this.fadeIn = false;
						this.transitionInLength = 1;
						this.transitionInProgress = 0;
					}
				}
				else if (fadeOut)
				{
					this.transitionOutProgress += ellapsedTime;

					this.SetVolume(100 - 100 * this.transitionOutProgress / this.transitionOutLength);

					if (this.transitionOutProgress >= this.transitionOutLength)
					{
						this.KillThis();
					}
				}

			}

			private bool killedOnPurpose = false;

			private void KillThis()
			{
				this.fadeIn = false;
				this.fadeOut = false;
				this.transitionInProgress = 0;
				this.transitionOutProgress = 0;
				this.status = PlayStatus.Stopped;
				this.MediaElement.Stop();
				this.MediaElement.Position = new TimeSpan(0);
				this.killedOnPurpose = true;
			}
			private static int counter = 0;
			private int ID = counter++;

			void MediaElement_MediaEnded(object sender, RoutedEventArgs e)
			{
				if (!this.killedOnPurpose)
				{
					switch (this.status)
					{
						case PlayStatus.Looping:
							this.MediaElement.Position = new TimeSpan(0);
							this.MediaElement.Play();
							break;

						case PlayStatus.PlayingOnce:
						case PlayStatus.Stopped:
						default:
							this.SetVolume(0);
							break;
					}
				}
				else
				{
					this.SetVolume(0);
				}
				this.killedOnPurpose = false;
			}

			private Music nowPlaying = null;

			private Music EnqueuedMusic = null;
			private long EnqueuedMusicStart = 0;
			

			public void PlayMusic(Music music)
			{
				int transitionMilliseconds = (int)music.TransitionLength.TotalMilliseconds;

				this.KillThis();
				this.killedOnPurpose = false;
				this.nowPlaying = music;
				this.MediaElement.Position = new TimeSpan(0);
				this.MediaElement.Volume = 0;
				if (music.InternalSound != null)
				{
					System.IO.Stream stream = music.InternalSound.MediaStream;
					if (stream != null)
					{
						this.MediaElement.SetSource(stream);
					}
					else
					{
						this.MediaElement.Source = null;
					}
				}
				this.MediaElement.Stop();
				this.status = music.Loop == MusicLoopStyle.LoopForever ? PlayStatus.Looping : PlayStatus.PlayingOnce;

				switch (music.Transition)
				{
					case MusicTransitionStyle.FadeOutCurrentAndPlay:
						this.EnqueuedMusicStart = this.GetCurrentMilliseconds() + transitionMilliseconds;
						this.EnqueuedMusic = music;
						this.SetVolume(0);
						break;

					case MusicTransitionStyle.FadeOutCurrentAndStop:
						this.SetVolume(0);
						this.status = PlayStatus.Stopped;
						break;

					case MusicTransitionStyle.FadeOutCurrentFadeInThisSimultaneously:
						this.transitionInLength = System.Math.Max(1, transitionMilliseconds);
						this.transitionInProgress = 0;
						this.fadeIn = true;
						this.SetVolume(0);
						break;

					case MusicTransitionStyle.Instant:
						this.SetVolume(100);
						this.MediaElement.Play();
						break;
				}
			}

			private void SetVolume(int value)
			{
				double dValue = value;
				if (this.nowPlaying != null)
				{
					dValue *= this.nowPlaying.VolumePercentage / 100.0;
				}
				this.MediaElement.Volume = dValue / 200;
			}

			public void EndMusic(Music newMusic)
			{
				int transitionMilliseconds = (int)newMusic.TransitionLength.TotalMilliseconds;

				switch (newMusic.Transition)
				{
					case MusicTransitionStyle.FadeOutCurrentAndPlay:
					case MusicTransitionStyle.FadeOutCurrentAndStop:
					case MusicTransitionStyle.FadeOutCurrentFadeInThisSimultaneously:
						this.fadeOut = true;
						this.transitionOutLength = System.Math.Max(1, transitionMilliseconds);
						this.transitionOutProgress = 0;
						break;

					case MusicTransitionStyle.Instant:
					default:
						this.KillThis();
						this.status = PlayStatus.Stopped;
						break;
				}
			}
		}
	}
}
